package com.qilin.hammer;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.qilin.hammer.util.ExcelHelper;


public class ExcelDataBuilder {
		
	private static void doMergeData(ArrayList<File> files, String dest){
		ArrayList<ArrayList<String>> data = new ArrayList<ArrayList<String>>();
		for(File file:files){
			String statuscode = file.getName();
			statuscode =  statuscode.substring(0, statuscode.lastIndexOf("."));
			ArrayList<ArrayList<String>> item = ExcelHelper.readFromCSV(file, 1);//ExcelHelper.readFromExcel(file, 1);
			for(ArrayList<String> row:item){
				row.add(statuscode);
				data.add(row);
			}
		}
		ExcelHelper.writeToExcel(data, dest);
	}
	
	//merge multi .csv to one xls file, use file name to be statuscode
	public static void mergeData(String dir){
		File root = new File(dir);
		File[] filesOrDirs = root.listFiles();
		ArrayList<File> files = new ArrayList<File>();
		for (int i = 0; i < filesOrDirs.length; i++) {
            if (!filesOrDirs[i].isDirectory()){
                files.add(filesOrDirs[i]);
            }
		}
		doMergeData(files,dir+"/data.xls");
	}
		
	class Pair{
		
		boolean hasLoop = false;
		int loop_count;
		int loops = 0;
		ArrayList<String> loop_act = null;
		
		String from = "";
		String to = "";
		int count;
		
	}
	
	public static String FIX_TYPE = "fixed";
	public static String RANDOM_TYPE = "random";
	public static String MULTIPLY_TYPE = "multiply";
	
	private static DocumentBuilderFactory domFactory=DocumentBuilderFactory.newInstance();
	private Map<String, Calendar> times = new HashMap<String, Calendar>();
	private Map<String, Integer> timestep_multiply = new HashMap<String, Integer>();
	private ArrayList<Pair> pairs = new ArrayList<Pair>();
	private ArrayList<String> path = new ArrayList<String>();
	private Map<String, String[]> extend_values= new HashMap<String, String[]>();
	private ArrayList<String> start_nodes = null;
	private Map<String, String> result = new HashMap<String, String>();
	//by default event duration between activities is 1 min
	private int interval = 1;
	private String unit = "m";
	private String duration_type = FIX_TYPE;
	//by default process begin time are equal
	private int begin_event_interval = 0;
	private String begin_event_unit = "m";
	
	public ExcelDataBuilder(){
	}
	
	private void setInterval(String time_step){
		String[] timestep = time_step.split(":");
		if(timestep.length == 2){
			String type = timestep[0];
			String[] values = timestep[1].split("\\s+");
			if(values.length == 2){
				if(type.equalsIgnoreCase("random"))
					this.duration_type = RANDOM_TYPE;
				else if(type.equalsIgnoreCase("multiply"))
					this.duration_type = MULTIPLY_TYPE;
				else
					this.duration_type = FIX_TYPE;
				this.interval = Integer.parseInt(values[0]);
				this.unit = values[1];
			}
		}
	}
	
	private void setBeginEventInterval(String time_step){
		String[] values = time_step.split("\\s+");		
		if(values.length == 2){
			this.begin_event_interval = Integer.parseInt(values[0]);
			this.begin_event_unit = values[1];
		}
	}
	
	
	
	private void increaseBeginEventTimeStep(Calendar time){
		if(this.begin_event_unit.equalsIgnoreCase("ms")){
			time.set(Calendar.MILLISECOND, time.get(Calendar.MILLISECOND)+this.begin_event_interval);
		} else if(this.begin_event_unit.equalsIgnoreCase("s")){
			time.set(Calendar.SECOND, time.get(Calendar.SECOND)+ begin_event_interval);
		} else if(this.begin_event_unit.equalsIgnoreCase("m")){
			time.set(Calendar.MINUTE, time.get(Calendar.MINUTE)+ begin_event_interval);
		} else if(this.begin_event_unit.equalsIgnoreCase("h")){
			time.set(Calendar.HOUR, time.get(Calendar.HOUR)+ begin_event_interval);
		} else if(this.begin_event_unit.equalsIgnoreCase("d")){
			time.set(Calendar.DAY_OF_MONTH, time.get(Calendar.DAY_OF_MONTH)+ begin_event_interval);
		} else if(this.begin_event_unit.equalsIgnoreCase("mon")){
			time.set(Calendar.MONTH, time.get(Calendar.MONTH)+ begin_event_interval);
		} else if(this.begin_event_unit.equalsIgnoreCase("y")){
			time.set(Calendar.YEAR, time.get(Calendar.YEAR)+ begin_event_interval);
		}
	}
	
	private int getTimeStep(int times){
		if(this.duration_type.equals(FIX_TYPE))
			return this.interval;
		else if(this.duration_type.equals(MULTIPLY_TYPE))
			return this.interval*times;
		else if(this.duration_type.equals(RANDOM_TYPE)){
			int temp = 0;
			while(temp == 0)
				temp = (int)(Math.random()*this.interval);
			return temp;
		}else
			return 0;
	}
	
	private void increaseTimeStep(Calendar time, int times){

		if(this.unit.equalsIgnoreCase("ms")){
				time.set(Calendar.MILLISECOND, time.get(Calendar.MILLISECOND)+getTimeStep(times));
		} else if(this.unit.equalsIgnoreCase("s")){
			time.set(Calendar.SECOND, time.get(Calendar.SECOND) +getTimeStep(times));
		} else if(this.unit.equalsIgnoreCase("m")){
			time.set(Calendar.MINUTE, time.get(Calendar.MINUTE)+ getTimeStep(times));
		} else if(this.unit.equalsIgnoreCase("h")){
			time.set(Calendar.HOUR, time.get(Calendar.HOUR)+ getTimeStep(times));
		} else if(this.unit.equalsIgnoreCase("d")){
			time.set(Calendar.DAY_OF_MONTH, time.get(Calendar.DAY_OF_MONTH)+ getTimeStep(times));
		} else if(this.unit.equalsIgnoreCase("mon")){
			time.set(Calendar.MONTH, time.get(Calendar.MONTH)+ getTimeStep(times));
		} else if(this.unit.equalsIgnoreCase("y")){
			time.set(Calendar.YEAR, time.get(Calendar.YEAR)+ getTimeStep(times));
		}

	}
	
	private Calendar getBeginTime(int id){
		Calendar time_begin = Calendar.getInstance();
		time_begin.set(Calendar.HOUR, 0);
		time_begin.set(Calendar.MINUTE, 0);
		time_begin.set(Calendar.SECOND, 0);
		time_begin.set(Calendar.MILLISECOND, 0);
		if(this.begin_event_interval > 0){
			for(int i=0; i<id; i++){
				this.increaseBeginEventTimeStep(time_begin);
			}
		}
		return time_begin;
	}
	
	private String getTimeStamp(Calendar timestamp){
		SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
		return format.format(timestamp.getTime());
	}

	private ArrayList<String> getDataItem(boolean isBeginEvent, String ...values){
		//values[0] should be event id
		ArrayList<String> dataitem = null;
		if(values.length > 1){
			if(!isBeginEvent){
				int multiply = timestep_multiply.get(values[0]);
				increaseTimeStep(times.get(values[0]), multiply);
				timestep_multiply.put(values[0], multiply+1);
			}
			dataitem = new ArrayList<String>();
			dataitem.add(values[0]);
			dataitem.add(this.getTimeStamp(times.get(values[0])));
			for(int i=1; i< values.length; i++){
				dataitem.add(values[i]);
			}
			if(extend_values.size() > 0){
				for(Entry<String, String[]> entry : extend_values.entrySet()){
					dataitem.add(entry.getValue()[Integer.parseInt(values[0])%entry.getValue().length]);
				}
			}
		}
		return dataitem;
	}
	
	private void doFillData(ArrayList<ArrayList<String>> data, String from, ArrayList<String> ids, int depth){
		//total events
		int total = ids.size();
		//count of data item
		int count = 0;
		//No events available to send
		if(total == 0)
			return;
		for(int i=0; i<depth; i++){
			if(path.get(i).equals(from)){
				System.out.println("Circle detected: "+path+"\nDon't support circle path, please use loop instead.");
				return;
			}
		}
		if(path.size() < depth+1)
			path.add(depth, from);
		else
			path.set(depth, from);
		for(Pair pair : pairs){
			if(pair.from.equals(from)){
				//The events are all consumed on this way
				if(pair.count == 0)
					continue;
				ArrayList<String> subids = new ArrayList<String>();
				int end = pair.count > total ? total : pair.count;
				for(int i=0; i<end; i++){
					String id = ids.get(count);
					data.add(this.getDataItem(false, id, pair.to));
					if(pair.hasLoop && i<pair.loop_count){
						for(int j=0; j<pair.loops; j++){
							if(pair.loop_act != null){
								for(String name:pair.loop_act){
									data.add(this.getDataItem(false, id, name));
								}
							}
							data.add(this.getDataItem(false, id, pair.from));
							data.add(this.getDataItem(false, id, pair.to));
						}
					}
					subids.add(id);
					count++;
				}
				doFillData(data, pair.to, subids, depth+1);
				if(pair.count == total){
					pair.count = 0;
					break;
				} else if(pair.count < total){
					pair.count = 0;
					total = total - count;
				} else {
					pair.count = pair.count - total;
					break;
				}
			}
		}
	}
	
	private void analyticPairs(Element root, ArrayList<Pair> allstartpairs) throws ParserConfigurationException, SAXException, IOException{
		NamedNodeMap attributes = root.getAttributes();
		for(int i=0; i<attributes.getLength(); i++){
			String name = attributes.item(i).getNodeName();
			String value = attributes.item(i).getNodeValue();
			if(name.equals("start")){
				start_nodes = new ArrayList<String>();
				for(String start_node : value.split(","))
					start_nodes.add(start_node);
			}else if(name.equals("timestep")){
				this.setInterval(value);
			}else if(name.equals("begin_time_diff")){ 
				this.setBeginEventInterval(value);
			}else {
				this.extend_values.put(name, value.split(","));
			}
		}
		NodeList nodes = root.getElementsByTagName("pair");
		for(int i=0; i<nodes.getLength(); i++){
			Pair pair= this.new Pair();
			Node node = nodes.item(i);
			pair.from = node.getAttributes().getNamedItem("from").getNodeValue();
			pair.to = node.getAttributes().getNamedItem("to").getNodeValue();
			pair.count = Integer.parseInt(node.getAttributes().getNamedItem("count").getNodeValue());
			if(node.hasChildNodes()){
				NodeList childnodes = node.getChildNodes();
				for(int j=0; j<childnodes.getLength(); j++){
					Node child = childnodes.item(j);
					//handle loop
					if(child.getNodeName().equals("loop")){
						pair.hasLoop = true;
						pair.loop_count =  Integer.parseInt(child.getAttributes().getNamedItem("count").getNodeValue());
						pair.loops = Integer.parseInt(child.getAttributes().getNamedItem("loops").getNodeValue());						
						//add activities into loop
						if(child.hasChildNodes()){
							NodeList loop_acts = child.getChildNodes();
							pair.loop_act = new ArrayList<String>();
							for(int k=0; k<loop_acts.getLength(); k++){
								Node actchild = loop_acts.item(k);
								if(actchild.getNodeName().equals("act"))
									pair.loop_act.add(loop_acts.item(k).getAttributes().getNamedItem("name").getNodeValue());
							}
						}
					}
				}
			}
			//collect all pairs except the one from start node
			if(start_nodes.indexOf(pair.from) == -1){
				pairs.add(pair);
			} else {
				allstartpairs.add(pair);
			}
		}
	}
	
	private boolean export(ArrayList<ArrayList<String>> data, String destfile){
		String tempDirect = System.getProperty("java.io.tmpdir");
		String zipFile = tempDirect+destfile+"/";
		File zipDirect = new File(zipFile);
		
		String singleDirect = tempDirect+destfile+"/single/";
		String multipleDirect = tempDirect+destfile+"/multiple/";
		File single = new File(singleDirect);
		File multiple = new File(multipleDirect);
		ArrayList<String> multiFiles = new ArrayList<String>();
		Map<String, ArrayList<ArrayList<String>>> multiFilesData= new HashMap<String, ArrayList<ArrayList<String>>>();
	    boolean isSuccess = zipDirect.mkdir() && single.mkdir() && multiple.mkdir();
	    ArrayList<String> head = data.get(0);
		if(isSuccess){
			//export single data
			ExcelHelper.writeToExcel(data, singleDirect+"data.xls");
			
			//export multiple data
			for(ArrayList<String> item : data){
				String statuscode = item.get(2);
				if(statuscode.equals("statuscode"))
					continue;
				if(multiFiles.indexOf(statuscode) == -1)
					multiFiles.add(statuscode);
				ArrayList<ArrayList<String>> fileData = multiFilesData.get(statuscode);
				if(fileData == null){
					fileData = new ArrayList<ArrayList<String>>();
					fileData.add(head);
					multiFilesData.put(statuscode, fileData);
				}
				fileData.add(item);
			}
			for(String file : multiFiles){
				String filename = multipleDirect + file +".xls";
				ExcelHelper.writeToExcel(multiFilesData.get(file), filename);
			}
			
			//zip the test data
			return ZipUtil.writeToZip(destfile);
		} else {
			System.out.println("Create directory fail");
		}
		return false;
	}
	
	//loan.xls
	public Map<String, String> createSampleData(String type, String conf, String destfile){
		ArrayList<Pair> start_pairs = new ArrayList<Pair>();
		try {
			Element root = null;
			DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
			Document config = null;
			if(type.equals("File")){
				config = domBuilder.parse(new File(conf));
			} else if (type.equals("String")){
				InputSource is = new InputSource();
		        is.setCharacterStream(new StringReader(conf));
		        config = domBuilder.parse(is);
			}
			if(config != null){
				root = config.getDocumentElement();	
				analyticPairs(root, start_pairs);
			} else {
				result.put("fail", "Fail to analytic the configuration");
				return result;
			}
		} catch (ParserConfigurationException e) {
			result.put("fail", "Fail to analytic the configuration:"+e.getMessage());
			e.printStackTrace();
			return result;
		} catch (SAXException e) {
			result.put("fail", "Fail to analytic the configuration:"+e.getMessage());
			e.printStackTrace();
			return result;
		} catch (IOException e) {
			result.put("fail", "IOException:"+e.getMessage());
			e.printStackTrace();
			return result;
		}
		if(start_nodes == null){
			System.out.println("Start activity is missing!");
			result.put("fail", "Start activity is missing");
			return result;
		}
		ArrayList<ArrayList<String>> data = new ArrayList<ArrayList<String>>();
		ArrayList<String> head = new ArrayList<String>();
		head.add("id");
		head.add("timestamp");
		head.add("statuscode");
		if(this.extend_values.size()>0){
			for(Entry<String, String[]> entry : extend_values.entrySet()){
				head.add(entry.getKey());
			}
		}
		data.add(head);
		int id = 1;
		for(Pair pair : start_pairs){
			//collect path from start node
			if(pair.count>0){
				ArrayList<String> ids = new ArrayList<String>();
				for(int i=0; i<pair.count; i++){
					times.put(String.valueOf(id), this.getBeginTime(id));
					timestep_multiply.put(String.valueOf(id), 1);
					data.add( this.getDataItem(true, String.valueOf(id), pair.from));
					data.add( this.getDataItem(false, String.valueOf(id), pair.to));
					//handle loop
					if(pair.hasLoop && i<pair.loop_count){
						for(int j=0; j<pair.loops; j++){
							if(pair.loop_act != null){
								for(String name:pair.loop_act){
									data.add(this.getDataItem(false, String.valueOf(id), name));
								}
							}
							data.add( this.getDataItem(false, String.valueOf(id), pair.from));							
							data.add( this.getDataItem(false, String.valueOf(id), pair.to));
						}
					}
					ids.add(String.valueOf(id));
					id++;
				}
				if(path.size() == 0)
					this.path.add(pair.from);
				else
					this.path.set(0, pair.from);
				this.doFillData(data, pair.to, ids, 1);
			}
	    }
		for(ArrayList<String> item:data){
			for(String s : item)
				System.out.print(s+" ");
			System.out.print("\n");
		}
		//ExcelHelper.writeToExcel(data, destfile);
		if(this.export(data, "test"))
			result.put("success", "");
		else
			result.put("fail", "Fail to export the data file");
		return result;
	}
	
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//ExcelDataBuilder.mergeData("D:/work/FeaturesTestOwned/PX/dataset/LoanDemo/LoanDemo");
		ExcelDataBuilder edbuilder = new ExcelDataBuilder();
		try {
			String prefix = "D:/work/FeaturesTestOwned/PX/dataset/sample/loan_attr_(multiply 10 d)";
			edbuilder.createSampleData("File", prefix+"/config.xml",prefix+"/data.xls");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
